package com.zhudi.math;

import com.ruoyi.common.exception.BusinessException;
import com.zhudi.domain.Expert;
import com.zhudi.domain.Graduation;
import com.zhudi.utils.MathUtil;
import com.zhudi.utils.ZhuDiUtil;
import org.apache.commons.collections.CollectionUtils;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 主观权重计算
 * @author chongjin
 */
public class Subjective {

    /**
     * 夹角余弦法计算每两类之间的接近程度
     * @param expertI 类i
     * @param expertJ 类j
     */
    public static BigDecimal calClose(Expert expertI, Expert expertJ) {
        BigDecimal[] valueListI = expertI.getWeights();
        BigDecimal[] valueListJ = expertJ.getWeights();
        if (valueListI.length != valueListJ.length) {
            throw new BusinessException(ZhuDiUtil.VECTOR_LENGTH_DIFFERENT);
        }
        BigDecimal valueI = null;
        BigDecimal valueJ = null;
        //分子
        BigDecimal numerator = BigDecimal.ZERO;
        //I平方和
        BigDecimal sumSquareI = BigDecimal.ZERO;
        //J平方和
        BigDecimal sumSquareJ = BigDecimal.ZERO;
        for (int i = 0; i < valueListI.length; i++) {
            valueI = valueListI[i];
            valueJ = valueListJ[i];
            //I*J累加 为分子
            numerator = numerator.add(valueI.multiply(valueJ));
            //I，J的平方和
            sumSquareI = sumSquareI.add(valueI.pow(2));
            sumSquareJ = sumSquareJ.add(valueJ.pow(2));
        }
        //I，J平方和 开方 相乘 得到分母
        BigDecimal denominator = MathUtil.getSqrt(sumSquareI).multiply(MathUtil.getSqrt(sumSquareJ));
        //相除得接近程度
        return numerator.divide(denominator, 4, BigDecimal.ROUND_HALF_UP);
    }

    /**
     * 根据规则循环比较所有类并合并新类
     * @param experts 需要比较的所有类
     */
    public static List<Expert> compareExpert(List<Expert> experts) {
        if (CollectionUtils.isEmpty(experts)) {
            throw new BusinessException(ZhuDiUtil.EXPERTS_EMPTY);
        }
        Expert[] maxExperts = new Expert[2];
        BigDecimal maxClose = BigDecimal.ZERO;
        BigDecimal calClose = null;
        for (int i = 0; i < experts.size(); i++) {
            for (int j = 0; j < i; j++) {
                //夹角余弦法计算每两类之间的接近程度
                calClose = calClose(experts.get(i), experts.get(j));
                //取最大值
                if (calClose.compareTo(maxClose) > 0) {
                    maxClose = calClose;
                    maxExperts[0] = experts.get(i);
                    maxExperts[1] = experts.get(j);
                }
            }
        }
        List<Expert> removeList = new ArrayList<>();
        removeList.add(maxExperts[0]);
        removeList.add(maxExperts[1]);
        //移除接近程度最大的两类
        experts.removeAll(removeList);
        BigDecimal[] weights = new BigDecimal[maxExperts[0].getWeights().length];
        BigDecimal average = null;
        //计算移除两类的算术平均数
        for (int i = 0; i < maxExperts[0].getWeights().length; i++) {
            average = maxExperts[0].getWeights()[i].add(maxExperts[1].getWeights()[i]);
            weights[i] = average.divide(MathUtil.TWO);
        }
        List<String> names = new ArrayList<>();
        names.addAll(maxExperts[0].getNames());
        names.addAll(maxExperts[1].getNames());
        Expert newExpert = new Expert();
        newExpert.setNames(names);
        newExpert.setWeights(weights);
        //比较列表加入合并的新类
        experts.add(newExpert);
        return experts;
    }

    /**
     * 计算指标主观权重向量
     * @param experts 原始主观权重数据
     */
    public static Graduation getSubject(List<Expert> experts) {
        int m = experts.size();
        int l = m;
        List<Expert> resultList = new ArrayList<>();
        resultList.addAll(experts);
        //判断 l=(m - 1) * 2 ，不满足则循环操作
        while (l != (m - 1) * 2) {
            //根据规则循环比较所有类并合并新类
            resultList = compareExpert(resultList);
            l++;
        }
        Map<String, BigDecimal> resultMap = new HashMap<>();
        BigDecimal expertNumber = null;
        BigDecimal contribution = null;
        BigDecimal sumContribution = BigDecimal.ZERO;
        BigDecimal M = new BigDecimal(m);
        for (Expert expert : resultList) {
            expertNumber = new BigDecimal(expert.getNames().size());
            for (String expertName : expert.getNames()) {
                contribution = expertNumber.divide(M, 4, BigDecimal.ROUND_HALF_UP);
                resultMap.put(expertName, contribution);
                sumContribution = sumContribution.add(contribution);
            }
        }
        BigDecimal[] weights = null;
        //专家贡献度
        BigDecimal percent = null;
        //专家主观权重向量原始数据
        BigDecimal[] expertWeight = null;
        //遍历原始专家列表
        for (Expert expert : experts) {
            //归一化
            percent = resultMap.get(expert.getNames().get(0)).divide(sumContribution, 4, BigDecimal.ROUND_HALF_UP);
            resultMap.put(expert.getNames().get(0), percent);
            expertWeight = expert.getWeights();
            if (null == weights) {
                weights = new BigDecimal[expertWeight.length];
                for (int i = 0; i < weights.length; i++) {
                    weights[i] = BigDecimal.ZERO;
                }
            }
            for (int i = 0; i < expertWeight.length; i++) {
                //累加 专家贡献度*专家主观权重向量
                weights[i] = weights[i].add(percent.multiply(expertWeight[i])).setScale(4, BigDecimal.ROUND_HALF_UP);
            }
        }
        Graduation graduation = new Graduation();
        graduation.setExpertContribution(resultMap);
        graduation.setSubjectiveData(weights);
        return graduation;
    }

}
